![]() ![]() ![]() ![]() ![]() |
Charlie Calvert's C++ Builder Unleashed
- 12 -Understanding Relational DatabasesIn order to make sure everyone is following the discussion in the next few chapters, I'm going to spend a few pages giving a quick-and-dirty introduction to relational databases. This discussion will also include a brief overview of the Database Desktop. My purpose here is to give a relatively concise explanation of what it means to use a relational, as opposed to a flat-file, database. Naturally, this will be a very broad overview of a complex and highly detailed subject. I am not attempting an academic analysis of this field of study, but instead want to provide a practical guide for everyday use. In this chapter, I will be working with Paradox tables and InterBase tables. Each database has its own unique set of rules. There is no definitive example of a relational database, any more than there is a definitive operating system or a definitive compiler. All databases have things in common, just as all compilers and all operating systems have things in common. As much as possible, I try to stress these common traits throughout this chapter. However, the specific implementation that I am referencing here is for Paradox and InterBase databases, and not everything I say will apply to Oracle or dBASE tables. In particular, this chapter is about the following:
If you already understand these subjects, you probably won't have much use for this chapter. If you need to review these subjects, or need to be introduced to them, you should read this chapter. Getting Started with Relational DatabasesThere are many different kinds of possible databases, but in today's world, there are only two kinds that have any significant market share for the PC:
Flat-file databases consist of a single file. The classic example would be an address book that contains a single table with six fields in it: Name, Address, City, State, Zip, and Phone. If that is your entire database, what you have is a flat-file database. In a flat-file database, the words table and database are synonymous. In general, relational databases consist of a series of tables related to each other by one or more fields in each table. In Chapter 9, "Using TTable and TDataSet," and Chapter 10, "SQL and the TQuery Object," you saw how to use the TTable and TQuery objects to relate the Customer and Orders tables together in a one-to-many relationship. As you recall, the two tables were joined on the CustNo field. The relationship established between these two tables on the CustNo field is very much at the heart of all relational databases. The Address program shown in Chapter 13, "Flat-File, Real-World Databases," is an example of a flat-file database. In Chapter 14, "Sessions and Relational Real-World Databases," you will see a second program, called KDAdd, which is a relational database. Here are three key differences between relational and flat-file databases:
Clearly relational databases are radically different from flat-file databases. Relational databases typically consist of multiple tables, at least some of which are related together by one or more fields. Flat-file databases, on the other hand, consist of only one single table, which is not related to any other table. Advantages of the Relational Database ModelWhat advantages do relational databases have over flat-file databases? Well, there are many strengths to this system; here are a few of the highlights:
To summarize, a relational database offers these possibilities:
As you can see, the three concepts that stand out when talking about relational databases are referential integrity, flexibility, and conservation of disk space. In this case, the word "flexibility" covers a wide range of broad features that can only be fully appreciated over time. The one disadvantage that relational databases have when compared to flat-file databases is that they are more complicated to use. This is not just a minor sticking point. Neophytes are often completely baffled by relational databases. They don't have a clue as to what to do with them. Even if you have a relative degree of expertise, anyone can still become overwhelmed by a relational database that consists of three dozen tables related to one another in some hundred different ways. (And yes, complexity on that scale is not uncommon in corporate America!) As you will see later in the book, almost the only way to work with big systems of that type is through case tools. Simple Set Logic: The Basis of Relational DatabasesThe basis for relational databases is a very simple form of mathematics. Each table represents a simple set that can be related to other tables through very fundamental mathematics. Because computers are so good at math, and particularly at integer math, they find relational databases easy to manipulate. One common feature of relational databases is that most records will have a unique number associated with them, and these numbers will be used as the keys that relate one table to another. This enables you to group tables together using simple mathematical relationships. In particular, you can group them using simple integer-based set arithmetic. For instance, in the Customers table from BCDEMOS, there is
a unique CustNo field in each record. Furthermore, the Orders table
has a unique OrderNo field associated with it. The Orders table
also has a CustNo field that will relate it to the Customer table.
The terminology of relational databases expresses these ideas by saying that the
Customer table has a primary key called CustNo, and the Orders
table has a primary key called OrderNo and a foreign key called CustNo:
Given this scenario, you can say "Show me the set of all orders such that their CustNo field is equal to X or within the range of X - Y." Computers love these kinds of simple mathematical relationships. It's their bread and butter. In essence, you are just asking for the intersection of two sets: "Show me the intersection of this record from the Customer table with all the records from the Orders table." This intersection will consist of one record from the Customer table with a particular CustNo plus all the records from the Orders table that have the same CustNo in their foreign key. These CustNo, OrderNo, AuthorNo, BookNo, and similar fields might also be used in flat-file databases as indexes, but they play a unique role in relational databases because they are the keys used to relate different tables. They make it possible to reduce the relationship between tables to nothing more than a simple series of mathematical formulas. These formulas are based on keys rather than on indexes. It is merely a coincidence that most keys also happen to be indexed. Viewing Indices and Keys in DBD or the ExplorerIn the next few sections I define primary and secondary keys, and describe how to use them. It might be helpful if I preface this discussion with a brief description of how to view keys using some of the tools that ship with BCB. This is just a preliminary look at this material. I cover it again in greater depth later in this chapter in a section called "Exploring the Indices in the BCDEMOS Database."
There are two ways to view the indexes and keys on a table. The best way is in the Database Explorer. Open up the Explorer and view the BCDEMOS database as shown in Figure 12.1. Click the Orders table and open up the Referential Constraints branch as shown in Figure 12.2. Notice that there are two constraints on this table, one called RefCustInOrders and the second called RefOrders. The RefCustInOrders field defined CustNo as a foreign key that relates to the CustNo field in the Customer table. A second way to view this key is in the Database Desktop. Set the Working Directory
from the File menu to BCDEMOS. Open up the Orders table in the
Database Desktop and select Table | Info structure from the menu. Drop down the Table
Properties and select Referential Integrity, as shown in Figure 12.3. FIGURE 12.2. The primary and foreign fields of the Orders table. FIGURE 12.3. Selecting Referential Integrity in the Database Desktop. Double-click RefCustInOrders to bring up the Referential Integrity dialog shown in Figure 12.4. FIGURE 12.4. The CustNo field in the Orders table relates to the CustNo field in the Customer table. The fields in the left side of this dialog belong to the Orders table. On the right is a list of all the tables in the database. In the center, you can see that the CustNo field has been selected from the Orders table and the CustNo field has been selected from the Customer table. The primary key of the Customer table is related to the foreign key of the Orders table. Now go back to the Database Explorer and open up the Indices branch of the Orders table, as shown in Figure 12.5. Note that you can see the names of the indexes, here labeled as <primary>
and as CustNo. The fields found in the indexes are also displayed. For instance,
you can see that the primary index consists of the OrderNo field and the
secondary index consists of the CustNo field. I am showing these to you so that you will begin to see the distinction between
keys and indexes. The two concepts are distinct. For further proof of this, open
up the IBLOCAL database in the Database Explorer. Use SYSDBA as
the user name, and masterkey as the password. Now open up the Employee project
table as shown in Figure 12.6. Note that there are separate listings for the index,
primary key, and foreign keys. In practice, almost all keyed fields will also have indexes. This leads people to think the two concepts are the same. However, indexes are about searching and sorting, and keys are about referential integrity. These distinctions will become blurred at times, but it helps if you can keep it in your mind that they are different ideas. The actual details concerning these distinctions will become clear in the next few pages. You can also see the indexes for a table inside the Database Desktop. To get started, open up the Orders table and select Table | Info Structure from the menu. The fields with the stars beside them are part of the primary index. Drop down the Table Properties combo box to view the secondary indexes. Double-click the indexes you see to view the details of their design. If you want to change the structure of a table, choose Table | Restructure from the menu, rather than Table | Info Structure. Most of the time, I find the Database Desktop is the right tool to use when I want to create or modify a table, and the Database Explorer is the right tool to use when I want to view the structure of a table. However, I often find myself jumping back and forth between the two tools, to get the best features of each. Later in the book I will talk about case tools, which are generally superior to either of the products discussed in this section. However, there are no case tools that ship with BCB, so I emphasize the universally available tools in this text. Throughout the ensuing discussion, you might have occasion to use the Database Explorer to examine the structure of the Customer, Orders, Items, and Parts tables. These are the tables I use when defining what relational databases are all about. Rule Numero Uno: Create a Primary Key for Each Table!The last two sections have introduced you to some of the key concepts in relational databases. If there is one lesson to take out of this chapter, it is the importance of creating a unique numerical key in the first field of most tables you create. This field is called a primary key. In both Paradox and InterBase, it is impossible to create a primary key without also simultaneously creating an index. If you want to have a list of addresses in a table, don't just list the Address, City, State, and Zip. Be sure to also include an integer-based CustNo, AddressNo, or Code field. This field will usually be both an index and the first field of the database. It is the primary key for your table, and must be, by definition, unique. That is, each record should have a unique Code field associated with it. The primary key
As I said earlier, the distinction between indexes and keys becomes blurred at times. However, they are distinct concepts and you should endeavor to discover the differences.
Just to make sure this is clear, I'll go ahead and list out the right and wrong way to create a table.
The first example is "correct" because it has a primary index called CustNo. It is declared as a unique Integer value. The second example is "wrong" because it omits a simple numerical field as the primary index.
Even if you don't yet understand how databases work, for now I would suggest automatically adding a simple numerical value in a primary index to all your tables. Do so even if you are not using the field at this time. Believe me, as you come to understand relational databases, you will see why I recommend doing this in most, though not all, cases. At this point, however, you will probably be better off creating the extra field and letting it go to waste, even if you don't understand why you are doing it. After you get a better feeling for relational databases, you will understand intuitively when the field is needed, and when you are encountering one of those rare occasions when it is going to be useless. When people first work with relational databases, they can get a little hung up about the overhead involved in creating all these extra key fields. The point to remember is that these fields allow the database to be treated as nothing more than sets of simple integers related together in various combinations. Computers fly through integer math. Adding these extra index fields to your tables makes your data become computer-friendly. Computers love those simple integer fields; your computer will show its thanks by running faster if you add them to your tables! Computers don't feel weighed down by the extra field any more than a car feels weighed down by a steering wheel, people feel weighed down by their hands, or a rose bush feels weighed down by a rose. Relational databases want you to add an extra integer field as a primary index to your tables! Remember, people like beautiful paintings, eloquent words, and lovely members of the opposite sex. Computers like logic. They like numbers, they like nice, clean, easily defined relationships! They like simple, integer-based primary keys in the first field of a table! One-to-Many Relationships: The Data and the IndexOne good way to start to understand relational databases is by working with the
Customer, Orders, Items, and Parts tables from
the BCDEMOS database. All four of these tables are related in one-to-many
relationships, each-to-each. That is, the Customer table is related to the
Orders table, the Orders table to the Items table, and
the Items table to the Parts table. (The relationship also works
in the opposite direction, but it may be simpler at first to think of it as going
in only one direction.)
Read the preceding table as a series of rows, starting left and moving to the right, as if they were sentences. The preceding list shows that the Customer and Orders tables are related in a one-to-many relationship, with Customer being the master table and Orders being the detail table. The connector between them is the CustNo field. That is, they both have a CustNo field. The CustNo field is the primary key of the Customer table and the foreign key of the Orders table. The OrderNo field is the primary key of the Orders table and a foreign key of the Items table. The PartNo field is the primary key of the Parts table and a foreign key of the Items table. The relationship between these tables can be reversed. For instance, the Parts table could become the master table and the Items table the detail table, and so on, back down the line. The reason you can reverse the relationship becomes clear when you think in purely mathematical terms. The Customer table has a series of CustNo fields. Say the CustNo for the first record is 1000. To get the Orders associated with that customer, you ask this question: "What are all the rows from the Orders table that have a CustNo of 1000?" That is: Select * from Orders where CustNo = 1000 Clearly, you could reverse this question. If you select a particular row from the Orders table, you could find which item from the Customer table it is related to by asking for the set of all Customer records with a CustNo of 1000. Because the CustNo field for the Customer table is a unique index, you will get only one record back. However, the way you relate the tables is still the same: Select * from Customer where CustNo = 1000 Working with Primary KeysThe Parts, Orders, Items, and Customer tables have various keys. As it happens, these keys are also indexes. An index enables you to sort tables on a particular field. A key helps you define the relationship between two tables, or otherwise group related bits of information by a set of predefined and automatically enforced rules. Unfortunately, sadly, and confusingly, you can still relate tables even without the presence of any keys or indexes. For instance, if there were no CustNo primary and foreign keys in the Customer and Orders tables, Paradox would still let you use SQL to relate the tables in a one-to-many relationship. However, in this scenario, performance would be slow because there is no index, and there would be no constraints on the data you could enter in the two tables because there would be no primary and foreign keys that define referential integrity. In this scenario you are back to the rosebush-without-a-rose phenomena. Yes, the tables are still part of a relational database, but they lack the features that make a relational database appealing. You need both the keys and the indexes to make a relational database appealing. I'll draw a distinction between only two different kinds of keys. The first kind I will discuss is called a primary key. The second is called a foreign key.
Creating a primary key enables you to have two people with the same name, but with different addresses. For instance, you can list a John Doe on Maple Street who has a CustNo of 25, and a John Doe on Henry Street who has a CustNo of 2000. The names may be the same, but the database can distinguish them by their CustNo. Once again, this shows why databases love those simple integer indexes. If the database had to sort on the address fields every time it tried to distinguish these two John Does, it would take a long time for the sort to finish. Computers can easily distinguish the number 25 from the number 2000, but it takes them longer to do a string compare on "Maple Street" and "Henry Street". Furthermore, just comparing the streets wouldn't be enough; you would also have to compare cities, states, and so on. If two entries with the same name were both missing addresses, the whole system would be in danger of falling apart altogether. The same thing would happen if two people named John Doe lived at the same address. Use those integer indexes; they make your life simpler! Working with Secondary Indices and Foreign KeysIt's now time to move on to a consideration of foreign keys. The CustNo field of the Orders table is a foreign key because it relates the Orders table to the primary key of the Customer table. It is also a secondary index which aids in sorting and searching through data. Indices also speed up operations such as joins and other master-detail relationships. When writing this section, I have found it difficult to totally divorce the idea of foreign key and secondary indexes. However, I will try to split them up into two categories, taking foreign keys first:
Here are some facts about secondary indexes:
If you are new to databases, you will undoubtedly be frustrated to discover that different databases have varying rules for setting up indexes, keys, and so on. In this book, I tend to use Paradox tables as the default, but I also spend considerable time describing InterBase tables. If you use some other database, such as dBASE, Oracle, or Sybase, you should be sure to read up on the basic rules for using those tools. For instance, some databases let you set up a foreign key that is not an index. In the Paradox and InterBase world, however, foreign keys are always accompanied by an index, so the two words become synonymous, particularly in the hands of people who don't really understand how relational databases work. The good news is that you will find that overall there are certain basic principles that define how databases work. The details may vary from implementation to implementation, but the fundamental ideas stay the same. Keys Are the Keys to the Kingdom!Let me take this whole paradigm even one step further. When I first looked at a database, I thought of it as a place to store information. After spending a lot of time with relational databases, I now think of them primarily as a way to relate bits of information through keys and indexes. I know this is putting the cart before the horse, but what really interests me about databases now is not the fact that they contain information per se, but that I can query them to retrieve related bits of information. In other words, I'm more interested in the logic that defines how tables relate to one another than I am in the information itself. No one can get excited about a list of addresses or a list of books. The lists themselves are very boring. What's interesting is the system of keys and indexes that relate tables together, and the various SQL statements you can use to ask questions against various sets of tables. When I picture a table, I see its primary and foreign keys as great big pillars, and I envision all the rest of the data as a little stone altar that is dwarfed by the pillars. Like a pagan temple, it's the pillars that you notice first; the altar is just a small stone structure you might overlook until someone points it out. Of course the temple is built around the altar, and databases are built around their data. But in practice it is easy to overlook the data. You care about the pillars, and you care about the primary and foreign keys. The rest tends to fade into the background. Give me a well-designed database with lots of interrelated tables and I can have fun asking it all sorts of interesting questions. It's not the data per se that is important, but the way the data is related! The act of properly relating a set of tables in a database is called, tragically enough, "normalizing" the data. Where this dreadful term came from I have no idea, but "normalizing" a database is the fun part of creating a database application. Exploring the Keys and Indices in the BCDEMOS DatabaseI am now going to look again at the tools that ship with BCB, and show how to use them to view and create indexes and keys. This examination of the subject will have greater depth than the quick overview presented earlier in this chapter. Here is a list of the indexes on the Customers, Orders, Items, and Parts tables:
Notice that the Items table has a composite primary index consisting of the OrderNo and ItemNo fields. It also has two secondary indexes, one on the OrderNo field and one on the PartNo field. The Parts table has two secondary indexes, one on the VenderNo, and one on the Description field. If you do not have a pre-made list like this one, you could find this information in at least four ways:
I will explain all these methods and then discuss some possible alternative techniques. If you drag the Customer table off the Explorer and onto a form, you will be able to view its Indices in the Object Inspector. If you drop down the IndexName property editor, you will see that there is one index listed there. This is the secondary index, called ByCompany. If you select this index, the table will sort on the Company field. If you set the IndexName property back to blank, the table will sort automatically on the primary index, which is the CustNo field. In other words, BCB never explicitly lists the primary index in the IndexName property editor. I suppose that the architects of the VCL assumed that all tables have a primary index, and that if you don't specify a particular index name, you want to sort on that index. Of course, it is not an error to create a table that has no primary index, and BCB can still work with that kind of table. You can also drop down the IndexFieldNames property, which gives you a list of the fields that are indexed, in this case the CustNo and Company fields. Here you can see the fields included in the primary index, but they are not marked as belonging to any particular index.
The IndexName and IndexFieldNames properties give you a handy way of tracking Indices at design time. They don't, however, give you all the information you might need, such as exactly what fields make up which parts of the primary and secondary Indices. In this case, you could probably guess, but it would still be nice to get a more definitive answer. If you open up the Database Explorer, expand the BCDEMOS node, the Tables node, the Customer node, and finally the Indices node, you get (naturally enough) a list of the Indices on the Customer table! This is a great feature, and you should use it whenever possible. Figure 12.7 shows the expanded nodes of the Indices for the Customer table. (The program kdAddExplore in the Chap14 subdirectory on the CD-ROM that accompanies this book uses the TSession object to do the same thing in a BCB program.) While you have the Explorer open, you should also expand the Fields node, as shown in Figure 12.8. This gives a quick list of all the fields and their types. Notice that you can drag and drop individual fields onto a form. A third way to get a look at the structure of a table is through the Database Desktop (DBD). You can open this program from the Tools menu in C++Builder. Use the File menu in the DBD to set the Working Directory to the BCDEMOS Alias. Open up the Customer table and choose the Table | Info Structure menu choice. Drop down the Table Properties combo box and look up the secondary Indices, as shown in Figure 12.9. The primary index is designated by the asterisks after the keyed fields in the Key Roster. In this case, only the CustNo field is starred, because it is the sole keyed field. FIGURE
12.7. The Indices of the Customer
table viewed in the Database Explorer.
Notice the Save As button on the Info Structure dialog. You can use this to save a table that contains the structure of the Customer table. You can then print this out on a printer using TQuickReports. Be sure to use a fixed-size font, not a proportional font: Field Name Type Size Key CustNo N * Company A 30 Addr1 A 30 Addr2 A 30 City A 15 State A 20 Zip A 10 Country A 20 Phone A 15 FAX A 15 TaxRate N Contact A 20 LastInvoiceDate @ In the example shown here, I have printed out only the first four fields of the table because of space considerations. (The fields are Field Name, Type, Size, and Key.) If I then recursively print out the structure of the table used to house the structure of the Customer table, I get the following report: Field Name Type Size Key Field Name A 25 Type A 1 Size S Key A 1 _Invariant Field ID S _Required Value A 1 _Min Value A 255 _Max Value A 255 _Default Value A 255 _Picture Value A 176 _Table Lookup A 255 _Table Lookup Type A 1 This is the same information found in the Data Dictionary, and it should prove sufficient under most circumstances. Using the Database Desktop to Create IndexesTo create a unique primary key in a Paradox table, open up the Database Desktop,
and create a table with the first field declared as an Integer or autoincrement value.
Place a star next to the first field, which tells Paradox to create a primary index
on it, as shown in Figure 12.10. To create a secondary index, drop down the table properties list and choose Secondary
Indices. (See Figure 12.11.) Click the Define button. Select the fields from your
table that you want to be part of your index. Click OK. A simple dialog will then
pop up asking you to name the index. I usually give the index a name based on the
fields being indexed. For instance, if I want to create an index on the CustNo
field, I would call the index CustNo, CustNoIndex, or ByCustNo.
If I wanted to create one on a field called Name, I would call the index
Name, NameIndex, or ByName. Using the Database Desktop to Create Primary and Foreign KeysTo create a primary or foreign key on a Paradox table you need to define something called referential integrity. You cannot define referential integrity without first defining primary keys on both tables involved. There also must be an index on the foreign key, but this index will be created automatically for you when you create the foreign key. In InterBase, the situation is somewhat different. The act of creating primary or foreign keys will automatically define indexes. As I said earlier, there are little variations on the main themes of relational databases, depending on what kind of database you use. In the Data subdirectory from the CD that ships with this book you will find two tables called MasterTable and DetailTable. Figure 12.10 shows how to use the Database desktop to create the MasterTable. These tables look like this, with the MasterTable listed first and the DetailTable listed second:
To create referential integrity between these two tables, you should open up the DetailTable in the Database Desktop. Open the Table | Restructure menu item. Select Referential Integrity from the Table Properties combo box. Click the Define button, and set things up so they look like they do in Figure 12.12. Click the OK button and give this relationship a name, such as RefMasterDetail. FIGURE 12.12. Defining referential integrity between the DetailTable and MasterTable. When you are done, you will have created primary keys and foreign keys on the MasterTable and DetailTable. The best way to see these keys is in the Database Explorer. On my system I used the BDE Configuration Utility to create an alias called CUnleashed that points at the Data subdirectory. If you open this alias in the Database Explorer and go to MasterTable, you can see the primary and foreign keys, which Paradox calls Primary and Foreign Fields. Why Use Referential Integrity?Referential integrity is one of the most valuable tools in a database programmer's arsenal. In particular, referential integrity will help guide the user so that they do not accidentally enter invalid data, or accidentally delete needed records. To see referential integrity in action, use the Database Desktop to enter two records in the MasterTable. The first should have the word Day in the Name field and the second should have the word Month in the Name field. You do not have to fill in the Code field, because it is an autoincrement field (+) and will be updated automatically.
In the DetailTable, enter in a few names of days of the week or months of the year in the SubName field. Give the MasterCode field a 1 if you are entering a day, and 2 if you are entering a month.
With this data in the tables, you could define a one-to-many relationship such that if you viewed the MasterTable record with Days in the Name field you would see only the days in the DetailTable, and if you selected Months, you would see only the month names from the DetailRecord. Referential integrity will do two things to help make sure that these tables stay in good shape. It will prevent you from deleting a record in the MasterTable that has detail records associated with it in the DetailTable. For instance, if you select the MasterTable, set the Database Desktop in Edit mode and press Control+Delete, you will not be able to delete a record from the MasterTable. Referential integrity will prevent you from entering a value in the MasterCode field of the DetailTable that is not in the primary key of the MasterTable. For instance, if you tried to enter the number 3 in the DetailTable's MasterCode field, you would get the error message "Master field missing". This is because there is no record in the MasterTable with a Code field of 3. Of course, if you added a record to the MasterTable with a Code field that had 3 in it, the database would let you enter the data. Needless to say, these rules are also enforced inside BCB. In your own programs, you might want to create exception handlers that would pop up messages that explained to the user exactly what was wrong, and why they could not perform a particular operation. Most users would not respond well to an exception that said no more than "Master field missing!" That is the end of my explanation of relational databases. In the last few pages you have learned about primary keys, foreign keys, indexes, referential integrity, and how all these pieces fit together to help you create robust applications. In the next few pages I will step you through some simple examples that illustrate these points. One-to-Many Relationships: The CodeNow that you know something about the data in the Customer, Orders,
Items, and Parts tables, it's time to link them together in a single
program called Relate. To get started, begin a new project and add a data
module to it. Place four TTable objects and four TDataSource objects
on the data module, wire each data source to a TTable object, and then wire
each of the TTable objects to one of the four tables mentioned earlier.
You can also rename the TTable and TDataSource objects so that
they correspond with their respective tables, as shown in Figure 12.13. Drop four TDBGrid objects on the main form for the project. Use the File | Include Unit Header menu option to link Form1 to DataModule1. Wire the grids to the datasources on the datamodule, making sure that each grid has its DataSource property assigned to a unique object. For instance, link the first grid to the Customer table, the second to the Orders table, and so on. Using the names visible in Figure 12.4, click the OrdersTable component and set its MasterSource property equal to CustomerSource, that is, set its MasterSource equal to the TDataSource object that is linked to the TTable object that hosts the Customer table. Set the ItemsTable MasterSource property equal to OrdersSource and the PartsTable MasterSource equal to ItemsSource. Click the OrdersTable MasterFields property and link up the Orders and Items tables on the CustNo field, as described in Chapter 9, "Using TTable and TDataSet." In the same way, hook up the TblItems to OrdersTable ká[infinity]the OrderNo field, and PartsTable to ItemsTable on the PartNo field. If you set all the tables to active and then run the program, the result should look like what you see in Figure 12.14. Spend a little time mucking about with this program. Notice, for instance, that if you change the selected item in the Customer table, the contents of the grids showing the Orders, Items, and Parts tables will change. In particular, notice that the CustNo in all the items in the Orders table is always equal to the CustNo in the currently selected item in the Customer table. The same thing can be said about the OrderNo field in the Orders and Items tables, and the PartNo field in the Items and Parts tables. In general, selecting one item at any level but the lowest in the hierarchy will
force many detail records to change. That is why these are called one-to-many relationships.
One record in the Orders table points to many records in the Items
and Parts tables.
This discussion of the Relate program has given you a look at some of the important features in the Database Explorer and Database Desktop. It has also given you a quick run-down on some of the key ideas behind the construction of relational databases. The point here is that C++Builder has lots of built-in tools that help you construct relational databases. There is more that I want to say about these topics, even in this rather sketchy overview of a complicated subject. In particular, I have not yet talked about joins. Relational Databases and JoinsIn the last section, you saw how to relate the Customers, Orders, Items, and Parts tables in a one-to-many relationship that is sometimes called a master-detail relationship. In this section, you will again relate all four tables, but in a different kind of relationship, called a join. You had a look at joins in the last chapter, "Working with Field Objects." This time the query that you need to build is a bit longer: SELECT DISTINCT d.Company, d1.AmountPaid, d2.Qty, d3.Description, d3.Cost, d3.ListPrice FROM "Customer.db" d, "Orders.db" d1, "Items.db" d2, "Parts.db" d3 WHERE (d1.CustNo = d.CustNo) AND (d2.OrderNo = d1.OrderNo) AND (d3.PartNo = d2.PartNo) ORDER BY d.Company, d1.AmountPaid, d2.Qty, d3.Description, d3.Cost, d3.ListPrice Though not horrendously complicated, the syntax shown here is still ugly enough to give some people pause. The basic principles involved in this kind of statement are simple enough to describe. All that's happening is that the Customer, Orders, Items, and Parts tables are being joined together into one large table of the type you would have to create if you were trying to track all this information in a single flat-file database. The one proviso, of course, is that not all the fields from the four tables are being used. In fact, the only ones mentioned are d.Company, d1.AmountPaid, d2.Qty, d3.Description, d3.Cost, d3.ListPrice Here the d, d1, d2, and d3 are described in the following From clause: "Customer.db" d, "Orders.db" d1, "Items.db" d2, "Parts.db" d3 The Order By clause, of course, simply defines the sort order to be used on the table created by this join. I am guilty here of using meaningless variable names. In general, you should choose identifiers more informative than d1 or d2. You can create a program that performs this join by dropping a TQuery, TDataSource, and TDBGrid on a form. Wire the objects together, wire the TQuery to the BCDEMOS database, and set its SQL property to the query shown previously. A sample program called FourWayJoin demonstrates this process. The output from the program is shown in Figure 12.15. If you are not familiar with this kind of join, you might want to bring up the Relate and FourWayJoin tables side by side and compare them. Look, for instance, at the Action Club entries in the FourWayJoin program and trace them through so that you see how they correspond to the entries in the Relate program. Both programs describe an identical set of relationships; they just show the outcome in a different manner. Notice that the AmountPaid column in the FourWayJoin program has the
same number repeated twice in the Action Club section, as shown in Figure 12.15.
In particular, the numbers $1,004.80 and $20,108 both appear twice. This is because
there are two different items associated with these orders, as you can tell from
glancing at the Parts table in the Relate program.
Joins and QBEThe FourWayJoin program is a good advertisement for the power of SQL. Once you had the SQL statement composed, it was simple to put the program together. All the work is embodied in just a few lines of code, and everything else was trivial to construct. SQL can help concentrate the intelligence of a program in one small area--or at least it does in this one example. The sticking point, of course, is that not everyone is a whiz at composing SQL statements. Even if you understand SQL thoroughly, it can still be confusing to try to string together all those interrelated Select, Order By, From, and Where clauses. What is needed here is a way to automate this process. Most of the versions of C++Builder ship with a very useful tool that makes it easy to compose even relatively complex SQL statements. In particular, I'm talking about the QBE tool in the Database Desktop. If you want, you can use the Query Builder instead, or some other third-party tool that you might favor. However, in this section of the book, I will concentrate on the QBE tool, because it will be available to nearly all readers of this book. (QBE is also built into Paradox. Furthermore, there are some third-party QBE components on the market. The Query Builder only ships with the client/server version of C++Builder or Delphi.) Start the DBD and set the Working Directory to the BCDEMOS alias. Choose
File | New | QBE Query from the menu. A dialog will appear listing the tables in
the BCDEMOS database. Select the Customer table. Reopen the Select
File dialog by clicking the Add Table icon in the Toolbar. You can find the Add Table
icon by holding the mouse over each icon until the fly-by help comes up or until
you see the hint on the status bar. You can also simply look for the icon with the
plus sign on it. Continue until you have added the Customer, Orders,
Items, and Parts tables to the query. You can multiselect from
inside the FileOpenDialog. Resize the query window until all four tables
are visible, as shown in Figure 12.16. To join these tables together, select the Join Tables icon, located just to the right of the lightning bolt. Click once on the Join Tables icon, and then click the CustNo fields for the Customer and Orders tables. The symbol "join1" will appear in each field. Click the Join Tables icon again, and link the Orders and Items tables on the OrderNo field. Join the Parts and Items tables on the PartNo field. After joining the tables, select the fields you want to show by clicking once
in the check box associated with the fields you want to view. When you are done,
the result should look like Figure 12.17. To test your work, click the lightning bolt icon once. You should get a table that looks just like the one in the FourWayJoin program. You will find a copy of this QBE query in the Chap12 directory on the CD-ROM that accompanies this book. To translate the QBE statement into SQL, first close the result table so you can view the query shown in Figure 12.17. Click once on the SQL icon to perform the translation. You can save this SQL to disk, or just block-copy it and deposit it in the SQL property of a TQuery object. On paper, this process takes a few minutes to explain. However, once you understand the QBE tool, you can use it to relate multiple tables in just a very few seconds. For most people, QBE is probably the simplest and fastest way to compose all your SQL Select statements. Don't neglect learning to use this tool. It's a simple, easy-to-use tool that can save you hours of time.
That's it for the discussion of the basic principles of relational databases. You've seen how to build master-detail relationships, and how to construct joins. More importantly, you've seen how C++Builder encapsulates these key aspects of relational database design. There is, of course, much more to the theory of relational databases. There are whole books on this subject, particularly on the best way to design relational databases. Which Database Should I Use?If you are not sure of which database to use, I would tentatively suggest using Paradox to get started. It has a robust set of rules for enforcing data integrity, a rich set of types, and some nice features such as autoincrement fields. It works fine on a network, as long as everyone can attach their PCs to one centralized server and you aren't expecting a large number of simultaneous users. If you are expecting 30 or more simultaneous users, I would bite the bullet financially and switch to InterBase or to another standard SQL server such as Oracle, Sybase, or MS SQL Server. You could have a hundred or even two hundred users hitting a Paradox table at the same time, but I wouldn't recommend it. If you have a hundred users, but only ten or fifteen are likely to be after a table at one time, I would still feel comfortable with Paradox, though I would start leaning in the direction of a real client/server database. Client/server databases such as InterBase will
Remember that when I make suggestions about databases or about anything else, I am usually not so much trying to establish a definitive standard as I am trying to give reasonable advice to those readers who are not sure which way to turn. SummaryMy suggestion at this point is to dig into relational databases and learn as much about them as you can. Raw data sitting on a disk is boring. Rows of data in a grid are boring. Relational databases, however, are innately interesting. This is the fun part of database programming. Play around with indexes, or play around with joins and one-to-many relationships. The name of the game here is to find ways to arrange data in relational tables so that you can get at it easily. When you arrange data correctly, it's amazing to see how quickly you can locate very obscure pieces of information. In fact, a number of very fun games, such as Civilization or the Ultima series, rely heavily on databases in order to further the game play. Take some time to dig into this stuff. It's more interesting than you might think. If you are wishing that I had spent more time on InterBase tables, don't worry, because I cover that topic heavily later in the book. Much of the material covered in this chapter will be reviewed again, in much shorter form, in the light of the InterBase server. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|